repo 多仓管理工具指南
项目规模到一定程度后,单个 Git 仓库往往装不下,会拆成几十上百个仓库。repo 是 Google 为管理 AOSP(Android 开源项目)开发的多仓管理工具:用一份 manifest 清单描述所有子仓库的地址与版本,提供统一的初始化、同步、分支与上传命令。本文覆盖从安装到日常开发的完整流程,包括 detached HEAD、分支无上游跟踪等新手常踩的坑,末尾附 manifest 格式字段速查(译自官方文档)。
核心概念
Repo 是一个建立在 Git 之上的仓库管理工具,用于管理由多个子 Git 仓库组成的大型项目。
- Manifest(清单):一个 XML 文件(通常是
default.xml),定义所有组成项目的 Git 仓库、存放路径、远程服务器地址以及应该检出的修订版本(revision,可以是分支、标签或提交哈希)。这是 Repo 的"大脑"。 - Project(项目):一个被 Repo 管理的独立 Git 仓库。
- Working Directory(工作目录):执行
repo init的目录,包含所有项目的源代码和一个隐藏的.repo/目录。 .repo/目录:包含 Repo 自身的代码、manifest 文件、每个项目的 Git 仓库(在.repo/projects/下)以及项目配置信息。不要手动修改这个目录。
安装与初始化
安装 Repo
# 在 Linux/macOS 上
mkdir ~/bin
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo
# 将 ~/bin 加入 PATH 环境变量
export PATH=~/bin:$PATH
# 将其添加到 shell 配置文件(~/.bashrc、~/.zshrc)中使其永久生效 初始化工作目录
# 创建并进入工作目录
mkdir my-project && cd my-project
# 初始化 Repo,指定 manifest 仓库的 URL 和要使用的 manifest 文件
repo init -u https://github.com/my-org/manifest-repo.git -b main -m default.xml -u:manifest 仓库的 URL。-b:manifest 仓库本身的分支。-m:要使用的 manifest 文件(在 manifest 仓库中)。省略时默认为default.xml。
同步所有代码
# 拉取所有项目代码,检出到 manifest 指定的修订版本
repo sync
# 常用选项:
# -c: 只同步 manifest 中指定的分支(节省流量和时间)
# -j4: 使用 4 个线程并行同步(根据 CPU 核心数调整)
# --no-tags: 不拉取标签(进一步节省流量)
repo sync -c -j4 --no-tags 同步完成后,各项目处于**分离头指针(detached HEAD)**状态。这是正常现象:repo 保证代码与 manifest 定义的精确版本一致,而不是跟踪某个分支的最新提交。
日常开发工作流
查看工作目录状态
# 查看所有项目的状态(是否有修改,当前在什么分支/修订版本上)
repo status
# 查看所有项目当前所在的分支
repo branch # 或 repo branches
# 查看 manifest 文件中定义的所有项目
repo list 创建功能分支
不要在 repo sync 后的分离头指针状态下直接提交代码,必须先创建分支。
# 方法一:使用 repo start(推荐)
# 为所有项目创建新分支(适用于系统级改动)
repo start my-feature --all
# 仅为特定项目创建新分支(适用于模块级改动)
repo start my-feature path/to/project1 path/to/project2
# 为当前所在目录的项目创建新分支
cd path/to/project
repo start my-feature .
# 方法二:使用原生 Git(需确认基于正确的基础版本)
# 先 repo sync 到正确的基础代码,再进入项目目录,基于当前提交创建分支
git checkout -b my-feature repo start 的优势:它基于 manifest 中定义的修订版本创建分支,而不是本地当前的 HEAD,确保所有开发者分支起点一致。如果分支已存在,repo start 会直接切换过去而不是报错。
修改和提交
# 1. 修改代码
# 2. 在项目目录中用标准 Git 命令提交
cd path/to/project
git add .
git commit -m "实现新功能"
# 3. 查看哪些项目有提交
repo status 上传代码审核(Gerrit)
如果项目使用 Gerrit 进行代码审查:
# 上传当前分支的提交到代码审查服务器
repo upload
# 仅上传特定项目
repo upload path/to/project1
# 常用选项:
# --reviewers=alice@example.com: 指定审核人
# --cc=bob@example.com: 抄送
# -t: 将本地分支名作为 topic(便于 Gerrit 上关联同一批变更) 更新代码到最新
别人提交代码后,更新本地分支的步骤:
# 1. 同步所有项目到 manifest 的最新定义
repo sync
# 2. 切换到功能分支(如果不在分支上)
repo start my-feature . # 分支已存在时会切换过去
# 3. 在功能分支上变基到最新上游代码
git rebase m/master 第 3 步有一个常见坑:repo start 创建的分支没有设置 upstream,直接执行裸 git rebase 会报错:
There is no tracking information for the current branch. 正确做法是显式指定 repo 维护的上游镜像引用(m/ 前缀):git rebase m/master,其中 master 换成 manifest 定义的上游分支名,可用 repo info . 查看。变基可能有冲突,需要手动解决。
处理冲突
repo sync 或 git rebase 后发生冲突时:
- 进入有冲突的项目目录。
git status查看冲突文件。- 手动编辑标记了
<<<<<<<、=======、>>>>>>>的文件解决冲突。 git add <file>标记冲突已解决。git rebase --continue继续变基。
分支管理
# 删除所有相关项目的某个分支(放弃这条分支的工作)
repo abandon my-feature-branch
# 查看所有项目中的分支情况
repo branch
# 在所有存在该分支的项目中切换到指定分支
repo checkout my-other-branch 高级技巧
加速同步
repo sync -c -j8 --no-tags --optimized-fetch 只同步特定项目
repo sync platform/build platform/bionic 在所有项目上执行命令
# 在所有项目中执行 git log -1
repo forall -c 'git log -1'
# 在有修改的项目中执行 git stash
repo forall -c 'git stash'
# 查看所有项目的远程 URL
repo forall -c 'git remote -v' -p 选项在执行命令前打印项目路径,输出多项目时便于定位。
清理和重置
# 丢弃所有项目的本地修改(不可恢复,执行前务必确认)
repo forall -c 'git reset --hard ; git clean -fdx' 查看差异
# 比较各项目工作区和暂存区的差异
repo diff 最佳实践
- 始终先
repo sync:开始新工作前,确保代码库是最新的。 - 用
repo start创建分支:这是基于正确修订版本创建分支的最安全方法。 - 小范围提交:尽量让提交只涉及相关的项目,而不是总是
--all。 - 频繁变基:经常用
git rebase m/<上游分支>将功能分支与主干保持同步,减小冲突积累。 - 理解
repo status输出:-表示项目没有修改,M表示文件内容修改,A表示文件添加,R表示文件重命名,??表示未追踪的新文件。 .repo/local_manifests/本地定制:可在此目录创建local_manifest.xml来覆盖或添加默认 manifest 中的设置,例如添加一个本地测试用的项目。
要点回顾
- repo 用一份 manifest 管理几十上百个 Git 仓库,
repo init+repo sync是入口,sync 后各项目处于 detached HEAD 是设计使然。 - 开发前必须
repo start建分支,提交用原生 Git 命令,上传审核用repo upload。 repo start的分支没有上游跟踪,更新代码时用git rebase m/<上游分支>而不是裸git rebase。- 需要本地定制仓库集合时,优先改
.repo/local_manifests/,不动主 manifest。
附:manifest 格式字段说明(官方文档翻译)
来源:repo 官方 manifest-format 文档,翻译整理。
Repo 清单描述了一个 repo 客户端的结构:哪些目录可见、应该从何处通过 git 获取。清单的基本结构是一个裸 Git 仓库,在其顶级目录中包含一个单独的 default.xml 文件。清单本身是版本控制的,对清单的更新会在 repo sync 期间由客户端自动获取。
为了跨 repo 版本的兼容性,所有未知元素都会被静默忽略。如果想使用自定义元素,x-* 命名空间被保留用于此目的。
manifest
文件的根元素。
notice
每当 repo sync 完成时显示给用户的任意文本。内容按清单中的原样传递。
remote
可指定一个或多个。每个 remote 指定一个或多个项目共享的 Git URL,以及(可选地)这些项目上传更改所通过的 Gerrit 代码审核服务器。
- name:在清单文件中唯一的短名称。用作每个项目的
.git/config中的远程名称,自动可用于git fetch、git remote、git pull和git push。 - alias:覆盖
name,将其设置为每个项目的.git/config中的远程名称。它的值可以重复,而name在清单文件内必须唯一。 - fetch:所有使用此远程的项目的 Git URL 前缀。每个项目的名称会附加此前缀,形成克隆项目的实际 URL。
- pushurl:所有使用此远程的项目的 Git push URL 前缀。可选;未指定时
git push使用与fetch相同的 URL。 - review:Gerrit 服务器的主机名,
repo upload将更改上传到该服务器。可选;未指定时repo upload无法工作。 - revision:Git 分支名(例如
main或refs/heads/main)。拥有自己revision的远程将覆盖默认的revision。
default
最多一个。其 remote 和 revision 属性在 project 元素未指定自己的值时使用。
- remote:先前定义的
remote元素的名称。 - revision:Git 分支名。未指定自身
revision的project使用此修订版本。 - dest-branch:Git 分支名。未设置自身
dest-branch的project继承此值;未设置时项目默认使用revision。 - upstream:可在其中找到 sha1 的 Git 引用名。在以
-c模式同步修订版本锁定的清单时使用,避免同步整个引用空间。 - sync-j:同步时使用的并行作业数。
- sync-c:设为
true时只同步给定分支(revision属性指定),而非整个引用空间。 - sync-s:设为
true时同时同步子项目。 - sync-tags:设为
false时只同步给定分支,不同步其他标签。
manifest-server
最多一个。url 属性指定清单服务器的 URL,该服务器是一个 XML RPC 服务。详见智能同步文档。
submanifest
可指定一个或多个。每个元素描述一个要作为子项检出的单个清单。
- name:子清单在当前(子)清单内的唯一名称,同时用作
revision的默认值。相同的名称可用于具有不同父清单的子清单。 - remote:先前定义的
remote元素的名称;未提供时使用default给出的远程。 - project:清单项目的名称。URL 的形成方式为
${remote_fetch}/${project_name}.git。项目名称必须与 Gerrit 所知的名称匹配(如果使用 Gerrit)。 - manifest-name:清单项目中的清单文件名;未提供时使用
default.xml。 - revision:Git 分支名、标签或提交哈希;未提供时使用
name。 - path:相对于 repo 客户端顶级目录的路径,放置子清单的顶级目录;未提供时使用
revision。 - groups:包含的子清单中所有项目所属的附加组,追加且递归。
- default-groups:init 时未指定
--groups=参数时要同步的清单组列表。
project
可指定一个或多个。每个元素描述一个要克隆到 repo 客户端工作区的单个 Git 仓库。可通过嵌套 project 指定 Git 子模块:子模块被自动识别并继承父级属性,也可被显式定义覆盖。
- name:项目的唯一名称。追加到远程的 fetch URL 形成
${remote_fetch}/${project_name}.git。不能为空、不能是绝对路径、不能使用.或..路径组件(本地清单中不强制)。 - path:相对于 repo 客户端顶级目录的本地检出目录;未提供时使用项目
name。限制同上。要将文件放入检出根目录,改用copyfile或linkfile。 - remote:先前定义的
remote元素的名称;未提供时使用default给出的远程。 - revision:清单希望为此项目跟踪的 Git 分支,可相对
refs/heads或绝对引用;标签和显式 SHA-1 理论上有效但未经广泛测试。 - dest-branch:
repo upload时更改提交到该分支进行代码审核;此处和default均未指定时使用revision。 - groups:项目所属的组,以空格或逗号分隔。所有项目都属于
all组,且每个项目自动属于name:<name>和path:<path>的组。例如<project name="monkeys" path="barrel-of"/>隐式位于default、name:monkeys和path:barrel-of组中。放在notdefault组中的项目不会被 repo 自动下载。 - sync-c / sync-s / upstream:语义同
default元素中的同名属性,作用于单个项目。 - clone-depth:获取此项目时使用的深度,覆盖
repo init --depth。 - force-path:设为
true时强制根据path(而非name)创建本地镜像仓库,仅适用于本地镜像同步。
extend-project
修改指定项目的属性,在本地清单中非常有用:不必完全替换现有项目定义即可修改其属性,对原始清单的更改更具鲁棒性。
- path:将更改限制为在指定路径检出的项目。
- dest-path:移动检出项目的位置(覆盖
path设置)。 - groups / revision / remote / dest-branch / upstream:覆盖原始项目的对应属性,语法同
project。 - base-rev:添加针对要扩展的修订版本的检查,不匹配时清单解析失败并给出列表。
annotation
零个或多个,作为 project 或 remote 的子元素。描述名称-值对:对项目,将在 forall 命令期间以 REPO_ 前缀导出到每个项目的环境。可选属性 keep(true/false,默认 true)确定 manifest 子命令导出时是否保留。
copyfile
零个或多个,作为 project 的子元素。repo sync 期间将项目内的 src 文件复制到客户端顶级的 dest 位置。src、dest 必须是文件,不允许目录或符号链接,中间路径也不能是符号链接;dest 的父目录缺失时自动创建。
linkfile
与 copyfile 同时运行,但创建符号链接而非复制。符号链接在 dest(相对树顶级)创建,指向项目中由 src 指定的路径。目标可以是文件或目录,但不能指向 repo 客户端外部。
remove-project
从内部清单表中删除一个项目,允许同一清单文件中的后续 project 元素用不同的源替换它。在本地清单中删除并替换项目时很有用。
- 仅指定
name时删除所有同名项目;同时指定name和path时只删除名称与路径都匹配的项目;仅指定path时删除所有匹配路径的项目。 - optional:设为
true时忽略没有匹配项目的元素。 - base-rev:语义同
extend-project的同名属性。
repo-hooks
一次只能指定一个,重新定义将导致解析失败。
- in-project:定义钩子的项目,值必须与先前定义的
project元素的name属性(而非path)匹配。 - enabled-list:要使用的钩子列表,以空格或逗号分隔。
superproject
指定超级项目的 URL。最多一个,重新定义将导致解析失败。
- name:语义同
project的name。 - remote:先前定义的
remote元素的名称。 - revision:清单希望为此超级项目跟踪的 Git 分支。
contactinfo
让清单作者自行注册联系信息。具有一个必需属性 bugurl(提交 bug 的 URL)。可以重复,后面的条目覆盖前面的。
include
将另一个清单文件包含进原始清单。目标清单必须能独立使用。
- name:要包含的清单,相对于清单仓库的根。
- groups:包含的清单中所有项目所属的附加组,追加且递归。
- revision:包含的清单中所有项目默认所属的分支。
本地清单
可在 $TOP_DIR/.repo/local_manifests/*.xml 中放置本地清单文件,添加额外的远程和项目:
$ ls .repo/local_manifests
local_manifest.xml
$ cat .repo/local_manifests/local_manifest.xml
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<project path="manifest"
name="tools/manifest" />
<project path="platform-manifest"
name="platform/manifest" />
</manifest> 在 repo sync 之前添加的项目会被自动下载和管理。